package com.aegis.gateway.filter;

import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.aegis.common.exception.AuthorizationException;
import com.aegis.common.web.BaseResponseEnum;
import com.aegis.core.enums.AegisHttpMethod;
import com.aegis.core.model.*;
import com.aegis.gateway.constants.FilterChainEnum;
import com.aegis.gateway.service.FilterManageService;
import com.aegis.core.utils.AegisUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.List;
import java.util.Optional;

/**
 * @Author wuweixin
 *
 * @Version 1.0
 * @Descritube order: -100
 */
@Component
@Slf4j
public class SaFilterAuthStrategy implements cn.dev33.satoken.filter.SaFilterAuthStrategy {


    @Autowired
    private FilterManageService filterManageService;

    @Override
    public void run(Object obj) {
        ServerWebExchange serverWebExchange = SaReactorSyncHolder.getContext();
        ServerHttpRequest request = serverWebExchange.getRequest();
        FilterChainEnum nextFilter = filterManageService.getNextFilter(serverWebExchange);
        if (nextFilter != null && SaReactorFilter.class != nextFilter.getFilterClass()) {
            return;
        }
        log.info("===> filter start: {}", getClass().getName());
        if (StrUtil.isBlank(StpUtil.getTokenValue())) {
            return;
        }
        //未登录则交由后续过滤器处理
        if (!StpUtil.isLogin()) {
            throw new AuthorizationException(BaseResponseEnum.UNAUTHORIZED, "请重新登录");
        }
        AegisSession loginUserInfo = AegisUserUtil.getLoginUserInfo();
        AegisUserTotalPermission userTotalPermission = AegisUserUtil.getUserTotalPermission(loginUserInfo.getAppId(),
                loginUserInfo.getUserId());

        if (userTotalPermission == null) {
            throw new AuthorizationException(BaseResponseEnum.NO_PERMISSION, "用户禁止访问该接口");
        }

        // 先判断 rejectApi，如果匹配直接抛异常，减少不必要的 allowApi 检查
        Optional.ofNullable(userTotalPermission.getRejectApi())
                .filter(CollectionUtil::isNotEmpty)
                .ifPresent(rejectApi -> {
                    if (CollectionUtil.isEmpty(rejectApi)) {
                        return;
                    }
                    if (isMethodMatch(rejectApi, request)) {
                        throw new AuthorizationException(BaseResponseEnum.NO_PERMISSION, "用户禁止访问该接口");
                    }
                });

        // 判断 allowApi
        List<AegisUserApi> allowApi = userTotalPermission.getAllowApi();
        if (CollectionUtil.isNotEmpty(allowApi)) {
            if (isMethodMatch(allowApi, request)) {
                filterManageService.filterChainEnd(serverWebExchange);
                return;
            }
        }

        // 判断用户所在用户组有无权限
        List<AegisAppGroupPermission> groupPermissions = userTotalPermission.getGroupPermissions();
        if (CollectionUtil.isNotEmpty(groupPermissions)) {
            for (AegisAppGroupPermission groupPermission : groupPermissions) {
                List<AegisUserApi> apis = groupPermission.getApis();
                if (CollectionUtil.isEmpty(apis)) {
                    continue;
                }
                if (isMethodMatch(apis, request)) {
                    filterManageService.filterChainEnd(serverWebExchange);
                    return;
                }
            }
        }

        // 只要 allowApi 为空或未匹配，都抛异常
        throw new AuthorizationException(BaseResponseEnum.NO_PERMISSION, "用户无权限访问该接口");

    }

    public boolean isMethodMatch(List<AegisUserApi> apis, ServerHttpRequest request) {
        for (AegisUserApi api : apis) {
            if (!SaRouter.isMatchCurrURI(api.getApi())) {
                continue;
            }
            AegisHttpMethod method = api.getMethod();
            if (method == null) {
                continue;
            }
            if (AegisHttpMethod.ALL == api.getMethod() || request.getMethod().matches(method.name())) {
                return true;
            }
        }
        return false;
    }
}
